#include "isoloader.h"
#include "debug.h"
#include "elf.h"
#include "reloc.h"
#include "syscall.h"
void print_to_screen(char*);

u8 Get_u8(SceUID fd, int addr){
	u8 ret = 0;
	sceIoLseek(fd, addr, PSP_SEEK_SET);
	sceIoRead(fd, &ret, sizeof(u8));
	return ret;
}
u16 Get_u16(SceUID fd, int addr){
	u16 ret = 0;
	sceIoLseek(fd, addr, PSP_SEEK_SET);
	sceIoRead(fd, &ret, sizeof(u16));
	return ret;
}
u32 Get_u32(SceUID fd, int addr){
	u32 ret = 0;
	sceIoLseek(fd, addr, PSP_SEEK_SET);
	sceIoRead(fd, &ret, sizeof(u32));
	return ret;
}
void Get_data(SceUID fd, int addr, void* data, int size){
	sceIoLseek(fd, addr, PSP_SEEK_SET);
	sceIoRead(fd, data, size);
}
void Get_string(SceUID fd, int addr, char* ret, int max_len){
	sceIoLseek(fd, addr, PSP_SEEK_SET);
	int i = 0;
	char c = 0x1;
	for(i=0; i<max_len && c!=0x0; ret[i]=c, i++){
		sceIoRead(fd, &c, sizeof(char));
	}
}

#define ISO_MAX_MODULES 8
static IsoModule modules[ISO_MAX_MODULES];

int IsoModuleManagerInit(){
	int i = 0;
	for(i=0; i<ISO_MAX_MODULES; i++){
		memset(&modules[i], 0, sizeof(IsoModule));
	}
	return 0;
}

IsoModule* IsoNewModule(){
	int i = 0;
	for(i=0; i<ISO_MAX_MODULES; i++){
		if(modules[i].used == 0){
			modules[i].used = 1;
			return &modules[i];
		}
	}
	return (IsoModule*)0x800200D1;
}

IsoModule* IsoGetModuleByUID(SceUID modid){
	int i = 0;
	for(i=0; i<ISO_MAX_MODULES; i++){
		if(modules[i].modid == modid){
			return &modules[i];
		}
	}
	return (IsoModule*)0x8002012E;
}

void GetModuleInfo(IsoModule* module){
	PspModuleInfo mod_info;
	module->text_addr = Get_u32(module->fd, 0x38);
	module->file_offset = module->text_addr;
	u32 mod_info_addr = Get_u32(module->fd, 0x40);
	module->text_size = mod_info_addr - module->text_addr;
	Get_data(module->fd, mod_info_addr, &mod_info, sizeof(PspModuleInfo));
	module->attribute = mod_info.exec_mode;
	module->version = mod_info.version;
	module->revision = mod_info.revision;
	strncpy(module->modname, mod_info.name, PSP_MODULE_MAX_NAME);
	module->gp = mod_info.gp;
	module->imports = mod_info.imports;
	module->imp_end = mod_info.imp_end;
}

void GetModuleImports(IsoModule* module){
	PspModuleImport import_vars;
	module->libs_count = (module->imp_end-module->imports) / sizeof(PspModuleImport);
	module->libs = (PspLibImport*)geMalloc(module->libs_count*sizeof(PspLibImport));

	int i = 0;
	for(i=0; i<module->libs_count; i++){
		Get_data(module->fd, module->imports+module->text_addr+sizeof(PspModuleImport)*i, &import_vars, sizeof(PspModuleImport));
		import_vars.name += module->text_addr;
		import_vars.nids += module->text_addr;
	//	import_vars.funcs += module->text_addr;
		Get_string(module->fd, import_vars.name, module->libs[i].name, PSP_LIB_MAX_NAME);
		module->libs[i].f_count = import_vars.f_count;

		module->libs[i].funcs = (PspEntry*)geMalloc(sizeof(PspEntry)*module->libs[i].f_count);
		int j = 0;
		for(j=0; j<module->libs[i].f_count; j++){
			module->libs[i].funcs[j].addr = import_vars.funcs + j*sizeof(u32)*2;
			module->libs[i].funcs[j].nid = Get_u32(module->fd, import_vars.nids + j*sizeof(u32));
		}
	}
}

u32 FindImport(IsoModule* module, const char* lib, u32 nid){
	int i=0, j=0;
	for(i=0; i<module->libs_count; i++){
		if(!strcmp(lib, module->libs[i].name)){
			for(j=0; j<module->libs[i].f_count; j++){
				if(module->libs[i].funcs[j].nid == nid){
					return module->libs[i].funcs[j].addr;
				}
			}
		}
	}
	return -1;
}

int relocate_entry2(tRelEntry* reloc_entry, void* reloc_addr, int iLoop, int relocsCount);
void RelocAccess(IsoModule* module){
	Elf32_Shdr sec_header;
	u32 strtab_offset;
	u32 cur_offset;
	u32 e_shoff = Get_u32(module->fd, 0x20);
	u16 e_shstrndx = Get_u16(module->fd, 0x32);
//	u16 e_phnum = Get_u16(module->fd, 0x26);
	u16 e_shnum = Get_u16(module->fd, 0x30);

	sceIoLseek(module->fd, e_shoff + e_shstrndx * sizeof(Elf32_Shdr), PSP_SEEK_SET);
	sceIoRead(module->fd, &sec_header, sizeof(Elf32_Shdr));
	strtab_offset = sec_header.sh_offset;

	cur_offset = e_shoff + sizeof(Elf32_Shdr);

	int i = 0;
	for(i=0; i<e_shnum; i++){
		sceIoLseek(module->fd, cur_offset, PSP_SEEK_SET);
		sceIoRead(module->fd, &sec_header, sizeof(Elf32_Shdr));
		char name[64] = "";
		Get_string(module->fd, strtab_offset+sec_header.sh_name, name, 64);
		LOGSTR3("Section %d/%d name : \"%s\"\n", i+1, e_shnum, (int)name);

		if(!strcmp(name, ".rel.text")){
			tRelEntry* reloc_entry = (tRelEntry*)(((int)module->text_addr) + ((int)sec_header.sh_offset) - ((int)module->file_offset));
			LOGSTR4("reloc_entry: 0x%08lX (0x%08lX+0x%08lX-0x%08lX)\n", (u32)reloc_entry, (int)module->text_addr, (int)sec_header.sh_offset, (int)module->file_offset);
			int num_entries = sec_header.sh_size / sizeof(tRelEntry);
			int j = 0;
			for(j=0; j<num_entries; j++){
	//			LOGSTR2("Reloc %d/%d :\n", j, num_entries);
	//			LOGSTR1("  offset: 0x%08lX\n", (u32)reloc_entry[j].r_offset);
	//			LOGSTR1("  opcode: 0x%08lX\n", ((u32*)module->text_addr+(int)reloc_entry[j].r_offset)[0]);
                relocate_entry2(&reloc_entry[j], (void*)module->text_addr, j, num_entries);
			}
			break;
		}

		cur_offset += sizeof(Elf32_Shdr);
	}
}

void ResolveImports(IsoModule* module){
	int i = 0;
	for(i=0; i<module->libs_count; i++){
		int j = 0;
		for(j=0; j<module->libs[i].f_count; j++){
			((u32*)module->libs[i].funcs[j].addr)[1] = estimate_syscall(module->libs[i].name, module->libs[i].funcs[j].nid, FROM_LOWER);
		}
	}
}

SceUID IsoLoadModule(const char* path){
	if(path == NULL){
		return 0x800200D3;
	}
	if(((u32)path & 0x88000000) < 0){
		return 0x800200D1;
	}
	SceUID fd = sceIoOpen(path, PSP_O_RDONLY, 777);
	if(fd < 0){
		return fd;
	}
	IsoModule* module = IsoNewModule();
	if((int)module < 0){
		sceIoClose(fd);
		return (SceUID)module;
	}

	module->fd = fd;

	GetModuleInfo(module);
	GetModuleImports(module);

	module->mod_size = sceIoLseek(fd, 0, PSP_SEEK_END) - module->text_addr;
	u32* ram_module = (u32*)geMalloc(module->mod_size);
	sceIoLseek(fd, module->text_addr, PSP_SEEK_SET);
	sceIoRead(fd, ram_module, module->mod_size);

	module->text_addr = (u32)ram_module;
	module->entry_addr = module->text_addr + Get_u32(fd, 0x18);

	LOGSTR0("Module info :\n");
	LOGSTR1("  Attribute: 0x%04X\n", module->attribute);
	LOGSTR2("  Version: %d.%d\n", module->version, module->revision);
	LOGSTR1("  Name: \"%s\"\n", (int)module->modname);
	LOGSTR1("  GP: 0x%08lX\n", module->gp);
	LOGSTR2("  exports: 0x%08lX to 0x%08lX\n", (module->exports+=module->text_addr), (module->exp_end+=module->text_addr));
	LOGSTR2("  imports: 0x%08lX to 0x%08lX\n", (module->imports+=module->text_addr), (module->imp_end+=module->text_addr));
	LOGSTR1("  text_addr: 0x%08lX\n", module->text_addr);
	LOGSTR1("  entry_addr: 0x%08lX\n", module->entry_addr);
	LOGSTR0("\n");

	char txt[128] = "";
	mysprintf1(txt, "  text_addr: 0x%08lX", module->text_addr);
	print_to_screen(txt);
	mysprintf1(txt, "  entry_addr: 0x%08lX", module->entry_addr);
	print_to_screen(txt);

	int i = 0;
	for(i=0; i<module->libs_count; i++){
		int j = 0;
	//	LOGSTR2("Import lib \"%s\" (%d) :\n", (int)module->libs[i].name, module->libs[i].f_count);
		for(j=0; j<module->libs[i].f_count; j++){
			module->libs[i].funcs[j].addr += module->text_addr;
	//		LOGSTR2("  Import NID 0x%08lX at 0x%08lX\n", module->libs[i].funcs[j].nid, module->libs[i].funcs[j].addr);
		}
	}

	
	module->modid = (int)path - (int)module->mod_size + (int)fd * (int)module->text_addr;
	module->mpid_text = 2;
	module->mpid_data = 2;
	module->data_size = module->mod_size - module->text_size;

	ResolveImports(module);
	LOGSTR0("\n");
	LOGSTR1("REAL LUI: 0x%08lX\n", ((u32*)module->text_addr)[0x000004D8/4]);
	LOGSTR0("\n");
	RelocAccess(module);

	sceIoClose(fd);
	return module->modid;
}

int IsoStartModule(SceUID modid, int args, void* argp){
	IsoModule* module = IsoGetModuleByUID(modid);
	if((int)module < 0){
		return (u32)module;
	}

	SceUID thid = sceKernelCreateThread("IsoModMgr_starter", (void*)(void*)module->entry_addr, 0x18, 0x1000, 0, NULL);
	LOGSTR1("  sceKernelCreateThread(\"IsoModMgr_starter\", ...) returned 0x%08lX\n", thid);
	int ret = sceKernelStartThread(thid, args, argp);
	LOGSTR1("  sceKernelStartThread(...) returned 0x%08lX\n", ret);

	return ret;
}
